from decimal import Decimal
from django.db.models import Q, Avg, Count
from products.models import Product
from typing import Dict, List, Tuple, Optional
import logging

logger = logging.getLogger(__name__)

class ProductRecommendationEngine:
    """
    产品推荐引擎
    基于用户风险偏好、投资金额和产品特征进行智能推荐
    """
    
    def __init__(self):
        self.risk_category_mapping = {
            1: {'stock': 0.1, 'bond': 0.4, 'fund': 0.3, 'cash': 0.2},  # 保守型
            2: {'stock': 0.2, 'bond': 0.3, 'fund': 0.3, 'cash': 0.2},  # 稳健型
            3: {'stock': 0.3, 'bond': 0.3, 'fund': 0.2, 'cash': 0.2},  # 平衡型
            4: {'stock': 0.4, 'bond': 0.2, 'fund': 0.3, 'cash': 0.1},  # 成长型
            5: {'stock': 0.5, 'bond': 0.1, 'fund': 0.3, 'cash': 0.1},  # 激进型
        }
    
    def get_recommended_products(self, risk_level: int, total_amount: Decimal, 
                               category_filter: Optional[str] = None) -> Dict[str, List[Dict]]:
        """
        获取推荐产品列表
        
        Args:
            risk_level: 用户风险等级 (1-5)
            total_amount: 投资总金额
            category_filter: 可选的分类过滤器
            
        Returns:
            按分类组织的推荐产品字典
        """
        try:
            allocation_ratios = self.risk_category_mapping.get(risk_level, self.risk_category_mapping[3])
            recommended_products = {}
            
            # 为每个资产类别推荐产品
            for category_code, ratio in allocation_ratios.items():
                if category_filter and category_code != category_filter:
                    continue
                    
                category_amount = total_amount * Decimal(str(ratio))
                products = self._get_products_by_category(
                    category_code, risk_level, category_amount
                )
                
                if products:
                    recommended_products[category_code] = products
            
            return recommended_products
            
        except Exception as e:
            logger.error(f"产品推荐失败: {e}")
            return {}
    
    def _get_products_by_category(self, category_code: str, risk_level: int, 
                                amount: Decimal, limit: int = 5) -> List[Dict]:
        """
        根据分类获取推荐产品
        
        Args:
            category_code: 分类代码 (stock, bond, fund, cash)
            risk_level: 用户风险等级
            amount: 该分类的投资金额
            limit: 返回产品数量限制
            
        Returns:
            推荐产品列表
        """
        try:
            # 分类名称映射
            category_name_mapping = {
                'stock': '股票',
                'bond': '债券', 
                'fund': '基金',
                'cash': '现金'
            }
            
            category_name = category_name_mapping.get(category_code)
            if not category_name:
                return []
            
            # 基础查询条件
            base_query = Q(
                category__name=category_name,
                status='active',
                min_investment__lte=amount
            )
            
            # 根据风险等级调整产品筛选
            risk_query = self._get_risk_based_query(risk_level, category_code)
            
            # 获取产品并排序
            products = Product.objects.filter(
                base_query & risk_query
            ).select_related('category').order_by(
                'risk_level',  # 优先推荐风险等级匹配的产品
                '-establishment_date',  # 较新的产品
                'management_fee'  # 费用较低的产品
            )[:limit]
            
            # 转换为字典格式并添加推荐理由
            result = []
            for product in products:
                product_info = {
                    'id': product.id,
                    'code': product.code,
                    'name': product.name,
                    'risk_level': product.risk_level,
                    'management_fee': float(product.management_fee),
                    'min_investment': float(product.min_investment),
                    'company': product.company,
                    'product_type': product.get_product_type_display(),
                    'recommendation_reason': self._get_recommendation_reason(
                        product, risk_level, category_code
                    ),
                    'suggested_amount': self._calculate_suggested_amount(
                        product, amount, len(products)
                    )
                }
                result.append(product_info)
            
            return result
            
        except Exception as e:
            logger.error(f"获取{category_code}分类产品失败: {e}")
            return []
    
    def _get_risk_based_query(self, risk_level: int, category_code: str) -> Q:
        """
        根据风险等级构建查询条件
        
        Args:
            risk_level: 用户风险等级
            category_code: 分类代码
            
        Returns:
            Django Q查询对象
        """
        # 根据用户风险等级调整产品风险等级范围
        if risk_level <= 2:  # 保守型、稳健型
            if category_code in ['stock']:
                return Q(risk_level__lte=3)  # 股票类产品风险不超过3级
            else:
                return Q(risk_level__lte=2)  # 其他产品风险不超过2级
        elif risk_level == 3:  # 平衡型
            return Q(risk_level__lte=4)  # 风险不超过4级
        else:  # 成长型、激进型
            return Q()  # 接受所有风险等级
    
    def _get_recommendation_reason(self, product: Product, risk_level: int, 
                                 category_code: str) -> str:
        """
        生成推荐理由
        
        Args:
            product: 产品对象
            risk_level: 用户风险等级
            category_code: 分类代码
            
        Returns:
            推荐理由字符串
        """
        reasons = []
        
        # 风险匹配度
        if product.risk_level <= risk_level:
            reasons.append("风险等级适合")
        
        # 费用优势
        if product.management_fee <= Decimal('0.01'):  # 管理费低于1%
            reasons.append("管理费用较低")
        
        # 公司声誉
        reputable_companies = ['华夏基金', '南方基金', '易方达基金', '广发基金', '嘉实基金', '博时基金', '天弘基金']
        if product.company in reputable_companies:
            reasons.append("知名基金公司")
        
        # 产品类型匹配
        type_mapping = {
            1: 'stable',
            2: 'stable', 
            3: 'balanced',
            4: 'growth',
            5: 'aggressive'
        }
        expected_type = type_mapping.get(risk_level, 'balanced')
        if product.product_type == expected_type:
            reasons.append("产品类型匹配")
        
        return "、".join(reasons) if reasons else "综合评估推荐"
    
    def _calculate_suggested_amount(self, product: Product, total_category_amount: Decimal, 
                                  product_count: int) -> float:
        """
        计算建议投资金额
        
        Args:
            product: 产品对象
            total_category_amount: 该分类总投资金额
            product_count: 该分类推荐产品数量
            
        Returns:
            建议投资金额
        """
        if product_count == 0:
            return 0
        
        # 平均分配，但考虑最小投资金额
        average_amount = total_category_amount / product_count
        suggested_amount = max(average_amount, product.min_investment)
        
        return float(suggested_amount)
    
    def get_portfolio_optimization_suggestions(self, current_holdings: List[Dict], 
                                             target_allocation: Dict[str, float]) -> List[Dict]:
        """
        获取投资组合优化建议
        
        Args:
            current_holdings: 当前持仓列表
            target_allocation: 目标配置比例
            
        Returns:
            优化建议列表
        """
        suggestions = []
        
        try:
            # 计算当前配置
            total_value = sum(holding.get('current_value', 0) for holding in current_holdings)
            current_allocation = self._calculate_current_allocation(current_holdings, total_value)
            
            # 比较当前配置与目标配置
            for category, target_ratio in target_allocation.items():
                current_ratio = current_allocation.get(category, 0)
                difference = target_ratio - current_ratio
                
                if abs(difference) > 0.05:  # 差异超过5%才建议调整
                    suggestion = {
                        'category': category,
                        'current_ratio': current_ratio,
                        'target_ratio': target_ratio,
                        'difference': difference,
                        'action': '增加' if difference > 0 else '减少',
                        'amount': abs(difference * total_value),
                        'priority': 'high' if abs(difference) > 0.15 else 'medium'
                    }
                    suggestions.append(suggestion)
            
            # 按优先级和差异大小排序
            suggestions.sort(key=lambda x: (x['priority'] == 'high', abs(x['difference'])), reverse=True)
            
        except Exception as e:
            logger.error(f"投资组合优化建议生成失败: {e}")
        
        return suggestions
    
    def _calculate_current_allocation(self, holdings: List[Dict], total_value: float) -> Dict[str, float]:
        """
        计算当前资产配置比例
        
        Args:
            holdings: 持仓列表
            total_value: 总价值
            
        Returns:
            当前配置比例字典
        """
        allocation = {'stock': 0.0, 'bond': 0.0, 'fund': 0.0, 'cash': 0.0}
        
        if total_value == 0:
            return allocation
        
        for holding in holdings:
            category = holding.get('category', '').lower()
            value = holding.get('current_value', 0)
            
            # 分类映射
            if '股票' in category:
                allocation['stock'] += value / total_value
            elif '债券' in category:
                allocation['bond'] += value / total_value
            elif '基金' in category:
                allocation['fund'] += value / total_value
            else:
                allocation['cash'] += value / total_value
        
        return allocation